package pl.kaczmarz.audiosteganography;

import java.nio.ByteOrder;

/**
 *
 * @author Pawel
 */
public class ByteConverter {
	private ByteOrder byteOrder;
	private boolean signed;

	public ByteOrder getByteOrder() {
		return byteOrder;
	}

	public void setByteOrder(ByteOrder byteOrder) {
		this.byteOrder = byteOrder;
	}

	public boolean isSigned() {
		return signed;
	}

	public void setSigned(boolean signed) {
		this.signed = signed;
	}


	public ByteConverter(ByteOrder ByteOrder, boolean signed) {
		this.byteOrder = ByteOrder;
		this.signed = signed;
	}

	/**
	 * Copy constructor
	 * @param source
	 */
	public ByteConverter(ByteConverter source) {
		this.byteOrder = source.byteOrder;
		this.signed = source.signed;
	}

	public final long toLong(byte[] input) {
		if (input.length == 0) {
			return 0;
		}

		long result = 0;
		int length = input.length;

		if (byteOrder == ByteOrder.BIG_ENDIAN) {
			for (int i = 0; i < length; ++i) {
				// 0xFF & x, promotes x to int and extract the first byte.
				// It is necessary, because int is a signed type and
				// bytes > 127 are treated as negative (in U2 system) and the
				// rest bits of the number are 1s
				result |= ((0xFF & input[i]) << ((length - i - 1) << 3));
			}
		} else {
			for (int i = 0; i < length; ++i) {
				result |= ((0xFF & input[i]) << (i << 3));
			}
		}

		// add trailing 1s to signed negative numbers:
		if (signed) {
			if (byteOrder == ByteOrder.BIG_ENDIAN) {
				if ((input[0] & 0x80) == 0x80) {
					result |= (0xFFFFFFFFFFFFFFFFL << (input.length << 3));
				}
			} else {
				if ((input[input.length - 1] & 0x80) == 0x80) {
					result |= (0xFFFFFFFFFFFFFFFFL << (input.length << 3));
				}
			}
		}

		return result;
	}

	public final int toInt(byte[] input) {
		if (input.length == 0) {
			return 0;
		}

		int result = 0;
		int length = input.length;

		if (byteOrder == ByteOrder.BIG_ENDIAN) {
			for (int i = 0; i < length; ++i) {
				// 0xFF & x, promotes x to int and extract the first byte.
				// It is necessary, because int is a signed type and
				// bytes > 127 are treated as negative (in U2 system) and the
				// rest bits of the number are 1s
				result |= ((0xFF & input[i]) << ((length - i - 1) << 3));
			}
		} else {
			for (int i = 0; i < length; ++i) {
				result |= ((0xFF & input[i]) << (i << 3));
			}
		}

		// add trailing 1s to signed negative numbers:
		if (signed) {
			if (byteOrder == ByteOrder.BIG_ENDIAN) {
				if ((input[0] & 0x80) == 0x80) {
					result |= (0xFFFFFFFF << (input.length << 3));
				}
			} else {
				if ((input[input.length - 1] & 0x80) == 0x80) {
					result |= (0xFFFFFFFF << (input.length << 3));
				}
			}
		}

		return result;
	}

	public final byte[] toBytes(long input, int size) {
		byte[] result = new byte[size];

		if (byteOrder == ByteOrder.BIG_ENDIAN) {
			for (int i = 0; i < size; ++i) {
				result[i] = (byte) ((input >> ((size - i - 1) << 3)) & 0xFF);
			}
		} else {
			for (int i = 0; i < size; ++i) {
				result[i] = (byte) ((input >> (i << 3)) & 0xFF);
			}
		}

		return result;
	}

	public final void toBytes(int input, byte[] output) {

		if (byteOrder == ByteOrder.BIG_ENDIAN) {
			for (int i = 0; i < output.length; ++i) {
				output[i] = (byte) ((input >> ((output.length - i - 1) << 3)) & 0xFF);
			}
		} else {
			for (int i = 0; i < output.length; ++i) {
				output[i] = (byte) ((input >> (i << 3)) & 0xFF);
			}
		}
	}

	public final void toBytes(long input, byte[] output) {

		if (byteOrder == ByteOrder.BIG_ENDIAN) {
			for (int i = 0; i < output.length; ++i) {
				output[i] = (byte) ((input >> ((output.length - i - 1) << 3)) & 0xFF);
			}
		} else {
			for (int i = 0; i < output.length; ++i) {
				output[i] = (byte) ((input >> (i << 3)) & 0xFF);
			}
		}
	}

	public final byte[] toBytes(int input, int size) {
		byte[] result = new byte[size];

		if (byteOrder == ByteOrder.BIG_ENDIAN) {
			for (int i = 0; i < size; ++i) {
				result[i] = (byte) ((input >> ((size - i - 1) << 3)) & 0xFF);
			}
		} else {
			for (int i = 0; i < size; ++i) {
				result[i] = (byte) ((input >> (i << 3)) & 0xFF);
			}
		}

		return result;
	}
}
